-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add class parameters, flags, and privateWithin and annotations to newClass in reflect API #21880
Add class parameters, flags, and privateWithin and annotations to newClass in reflect API #21880
Conversation
* | ||
* Parameters can be obtained via classSymbol.memberField | ||
*/ | ||
@experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr], paramNames: List[String], paramTypes: List[TypeRepr], flags: Flags, privateWithin: Symbol): Symbol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be possible to reformulate such that parents
is also a function (to support param refs?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that it should be a Symbol => List[TypeRepr]
type? I have some trouble visualizing where we would need that classSymbol to define the parent TypeRepr (class Cls extends Cls.InnerCls
?, is that legal?). Could you give me an example of a tree like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just saw class Cls extends Cls.InnerCls
in the wild but in that case, Cls also had a companion object which contained InnerCls - not sure if that is possible to define right now but I'll have to look into the newModule method and see if it also needs updates
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean something like class anon(x: Int) extends Box[x.type]
- I don't know the best solution, but this would be good to be able to support I assume - same with tracked eventually, but maybe that can be cleanly added later?
28cb854
to
33fbd5f
Compare
import quotes.reflect.* | ||
|
||
val name = nameExpr.valueOrAbort | ||
val parents = List(TypeTree.of[Object]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there should be a test case where the parent needs an argument supplied via the constructor parameters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried doing that in the not-very-well-named tests/run-macros/newClassParamsExtendsClassParams, which generates something like this:
'{
class `name`(idx: Int) extends Foo(idx)
new `name`(22)
}
Or do you mean something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that looks good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also such test cases could be added:
- Creating a trait with Symbol.newClass (is this possible?)
- Creating an abstract class with Symbol.newClass
- trait with parameter extending another trait with parameter (not sure about this)
- class with multiple parameters (this probably not needed)
- extending java class with parameter
To be clear, this is not ready, I did put this up since I wanted some feedback if anyone had some (Thank You @bishabosha!) and did not want to rush it last minute, that's why it a draft |
a839263
to
81cea85
Compare
paramNames: List[String], | ||
paramTypes: List[TypeRepr], | ||
clsFlags: Flags, | ||
clsPrivateWithin: Symbol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some nit comments.
Since it is public API, maybe align argument names with already existing? E.G. flags
and not clsFlags
, privateWithin
and not clsPrivateWithin
clsPrivateWithin: Symbol, | ||
consFlags: Flags, | ||
consPrivateWithin: Symbol, | ||
conParamFlags: List[List[Flags]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
constructor related arguments may have same prefix (now there are 3 constructor
, cons
and con
) and maybe put all constructor related arguments together after everything else
Want this very much 👀 |
@jchyb @nicolasstucki Can I somehow help proceeding with this? Maybe contribute with a spree? |
It's pretty much done in terms of the difficult stuff. I still need to add some test cases (your suggestions should help, thank you!), docs and perhaps more checks for flags. I planned for this for 3.7, and we still have some time for that, thus the seemingly slow progress. |
30abd4f
to
85fdd02
Compare
This is planned to be included in 3.7.0. |
3ee6271
to
c385ec1
Compare
c385ec1
to
32f484a
Compare
@hamzaremmal @smarter Ideally once we update these methods we'll be able to stabilize them before the next LTS. That is to say, I would really like to merge this before the end of the current cycle, for 3.7. It might seem like a lot of lines of code, but most of those are the added tests and docs - the real changes are all in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a first part of my review. I'm still looking at QuotesImpl
and the tests. I will update the status or submit a new review when I finish.
@@ -3837,8 +3838,171 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |||
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be | |||
* direct or indirect children of the reflection context's owner. | |||
*/ | |||
// TODO: add flags and privateWithin | |||
@experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol | |||
@experimental def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the name of a parameter is not something we can do as we wish, since users might be using named parameters in their code and this change can break it. However, this is an experimental method, so we are free to do this change for the time being.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To align with everything else, shouldn't we also change this?
@experimental def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol | |
@experimental def newClass(owner: Symbol, name: String, parents: Symbol => List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Not related, but we could extract it into its own issue) I'm not sure how much this change can be useful but it would for sure add expressiveness.
@experimental def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol | |
@experimental def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Symbol => Option[TypeRepr]): Symbol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to keep that overload of newClass simple and adding this would break current Scalamock implementation (which we should not worry about too much since this method is experimental, but I just did not think it was necessary)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, as I said, it's not related, but we should consider it at some point.
@@ -1379,13 +1379,13 @@ object SourceCode { | |||
printTypeTree(bounds.low) | |||
else | |||
bounds.low match { | |||
case Inferred() => | |||
case Inferred() if bounds.low.tpe.typeSymbol == TypeRepr.of[Nothing].typeSymbol => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a cosmetic change and it only hides when we infer Nothing
(or Any
for the second change). What was the motivation you had behind it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had an issue that when I built a tree like class SomeClass[T <: Int]
and would call .show on it, it would only render class SomeClass[T]
. It turned out that trees created with tpd.TypeTree are treated as Inferred in the Quotes reflect api, and if I wanted to actually render something, I needed this change here (I figure it might be better to have a printout a bit too big, than not big enough). I recall trying to test if it was possible to adjust the reflect Inferred tree definition instead, but I do not remember what the problem there was. Test case where this matters is newClassTypeParams
.
* val clsDef = ClassDef(cls, parents, body = List(fooDef)) | ||
* val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), List(idxExpr.asTerm, strExpr.asTerm)) | ||
* | ||
* Block(List(clsDef), Apply(Select(newCls, cls.methodMember("foo")(0)), Nil)).asExprOf[Unit] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first overload and the next one have a splice version of the resulting tree in the documentation. This is extracted from the first one:
* constructs the equivalent to
* ```
* '{
* class myClass() extends Object with Foo {
* def foo(): Unit = println("Calling foo")
* }
* new myClass(): Foo
* }
* ```
We should also add that here imho.
parentTypes: Symbol => List[Type], | ||
selfInfo: Type = NoType, | ||
privateWithin: Symbol = NoSymbol, | ||
annotations: List[Tree] = Nil, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
newNormalizedClassSymbol
doesn't actually have the annotations
parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this one myself, we have to set annotations in the denotation, and I thought it would be convenient to do it where we set other denotation related fields
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I was just flagging one of the differences between your new method and newNormalizedClassSymbol
. You shouldn't consider it as an actionable item.
/** Same as `newNormalizedClassSymbol` except that `parents` can be a function returning a list of arbitrary | ||
* types which get normalized into type refs and parameter bindings. | ||
*/ | ||
def newNormalizedClassSymbolUsingClassSymbolinParents( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm again not against overloading, but can't we keep the same name as newNormalizedClassSymbol
. The proposed one is very (very) long...
def newNormalizedClassSymbolUsingClassSymbolinParents( | |
def newNormalizedClassSymbol( |
val parents = parentTypes(cls) | ||
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") | ||
denot.info = ClassInfo(owner.thisType, cls, parents.map(_.dealias), decls, TermRef(owner.thisType, module)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should dealias
before checking if the first parent is a trait.
val parents = parentTypes(cls) | |
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") | |
denot.info = ClassInfo(owner.thisType, cls, parents.map(_.dealias), decls, TermRef(owner.thisType, module)) | |
val parents = parentTypes(cls).map(_.dealias) | |
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") | |
denot.info = ClassInfo(owner.thisType, cls, parents, decls, TermRef(owner.thisType, module)) |
/** Same as `newNormalizedModuleSymbol` except that `parents` can be a function returning a list of arbitrary | ||
* types which get normalized into type refs and parameter bindings. | ||
*/ | ||
def newNormalizedModuleSymbolUsingClassSymbolInParents( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same remarks here as for newNormalizedClassSymbolUsingClassSymbolinParents
def newNormalizedModuleSymbolUsingClassSymbolInParents( | |
def newNormalizedModuleSymbol( |
8190f1c
to
70f3eca
Compare
70f3eca
to
f4a06bd
Compare
1575dca
to
aaef412
Compare
@lihaoyi this should mean you can remove the hacks in Mill for Cross Module macro |
…Class in reflect API (#21880) Instead of replacing the one newMethod we have, we instead add two more with varying complexity (similarly to how newMethod is handled). This is also so we can keep the initial newClass implementation (the one creating newClass with public empty primary constructor) intact, which despite being experiemental - already sees use in libraries and projects. Fixes #21739 and addresses some old TODOs (from the stdlibExperimentalDefinitions.scala file).
Instead of replacing the one newMethod we have, we instead add two more with varying complexity (similarly to how newMethod is handled). This is also so we can keep the initial newClass implementation (the one creating newClass with public empty primary constructor) intact, which despite being experiemental - already sees use in libraries and projects.
Fixes #21739 and addresses some old TODOs (from the stdlibExperimentalDefinitions.scala file).